package com.binarycat.dynamiclimit.initialization;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;
import org.springframework.web.method.HandlerMethod;

import com.binarycat.dynamiclimit.annotation.DynamicLimit;
import com.binarycat.dynamiclimit.bean.DynamicLimitData;
import com.binarycat.dynamiclimit.bean.LimitPolicyEnum;
import com.binarycat.dynamiclimit.constant.CommonLimitConstants;
import com.binarycat.dynamiclimit.limiter.policy.AbstractLimitPolicy;
import com.binarycat.dynamiclimit.limiter.policy.CpuLoadLimitPolicy;
import com.binarycat.dynamiclimit.limiter.policy.ExceptionRateLimitPolicy;
import com.binarycat.dynamiclimit.limiter.policy.MemoryUsageRateLimitPolicy;
import com.binarycat.dynamiclimit.limiter.policy.TimeoutRateLimitPolicy;
import com.binarycat.dynamiclimit.metric.SlidingHistory;
import com.binarycat.dynamiclimit.util.CommonLimitUtils;
import com.codahale.metrics.MetricRegistry;

/**
 * 度量信息工厂
 * 
 * @author jjb
 * @date 2018/08/11
 */
public class MetricFactory extends AbstractInitializationFactory {

    /**
     * 注册相关的度量工具
     * 
     * @param metricRegistry
     * @param handlerMethod
     * @return true:注册完成,false:无需注册
     */
    public static boolean registryMetric(DynamicLimitData dynamicLimitData, HandlerMethod handlerMethod) {
        return toRegistryMetric(dynamicLimitData, handlerMethod, null);
    }

    /**
     * 
     * @param dynamicLimitData
     * @param handlerMethod
     * @param key
     * @return
     * @throws IllegalArgumentException
     */
    private static boolean toRegistryMetric(DynamicLimitData dynamicLimitData, HandlerMethod handlerMethod,
        String customkey) throws IllegalArgumentException {
        MetricRegistry metricRegistry = dynamicLimitData.getMetricRegistry();
        DynamicLimit classDynamicLimit = handlerMethod.getBeanType().getAnnotation(DynamicLimit.class);
        DynamicLimit methodDynamicLimit = CommonLimitUtils.getDynamicLimitAnnotation(handlerMethod);

        if (!checkNeedRegistryMetric(classDynamicLimit, methodDynamicLimit)) {
            return false;
        }
        String key = handlerMethod.toString();
        if (!StringUtils.isBlank(customkey)) {
            key = customkey;
        }
        metricRegistry.meter(MetricRegistry.name(key, CommonLimitConstants.METRIC_QPS));
        LimitPolicyEnum limitPolicy =
            methodDynamicLimit != null ? methodDynamicLimit.limitPolicy() : classDynamicLimit.limitPolicy();

        Map<String, List<String>> limitPolicyMethodsMap = dynamicLimitData.getLimitPolicyMethodsMap();

        classifyMethodNameByLimitPolocy(key, limitPolicyMethodsMap, limitPolicy);
        registerMetricByLimitPolocy(metricRegistry, key, limitPolicy);

        return true;
    }

    /**
     * @param metricRegistry
     * @param key
     * @param isAllPolicy
     * @param isExecuteResultLimit
     * @param isTimeoutLimit
     * @param isExceptionLimit
     * @throws IllegalArgumentException
     */
    private static void registerMetricByLimitPolocy(MetricRegistry metricRegistry, String key,
        LimitPolicyEnum limitPolicy) throws IllegalArgumentException {

        boolean isAllPolicy = limitPolicy.compareTo(LimitPolicyEnum.ALL_POLICY_LIMIT) == 0;
        boolean isExecuteResultLimit = limitPolicy.compareTo(LimitPolicyEnum.EXECUTE_RESULT_LIMIT) == 0;
        boolean isTimeoutLimit = limitPolicy.compareTo(LimitPolicyEnum.TIMEOUT_LIMIT) == 0;
        boolean isExceptionLimit = limitPolicy.compareTo(LimitPolicyEnum.EXCEPTION_LIMIT) == 0;

        if (isAllPolicy || isExecuteResultLimit) {
            metricRegistry.register(MetricRegistry.name(key, CommonLimitConstants.METRIC_TIMEOUT),
                new SlidingHistory(2));
            metricRegistry.register(MetricRegistry.name(key, CommonLimitConstants.METRIC_EXCEPTION),
                new SlidingHistory());
            return;
        }

        if (isTimeoutLimit) {
            metricRegistry.register(MetricRegistry.name(key, CommonLimitConstants.METRIC_TIMEOUT),
                new SlidingHistory(2));
            return;
        }

        if (isExceptionLimit) {
            metricRegistry.register(MetricRegistry.name(key, CommonLimitConstants.METRIC_EXCEPTION),
                new SlidingHistory());
        }
    }

    /**
     * @param key
     * @param limitPolicyMethodsMap
     * @param limitPolicy
     */
    private static void classifyMethodNameByLimitPolocy(String key, Map<String, List<String>> limitPolicyMethodsMap,
        LimitPolicyEnum limitPolicy) {
        boolean isAllPolicy = limitPolicy.compareTo(LimitPolicyEnum.ALL_POLICY_LIMIT) == 0;
        boolean isUsageRateLimit = limitPolicy.compareTo(LimitPolicyEnum.USAGERATE_LIMIT) == 0;
        boolean isExecuteResultLimit = limitPolicy.compareTo(LimitPolicyEnum.EXECUTE_RESULT_LIMIT) == 0;
        boolean isCpuLimit = limitPolicy.compareTo(LimitPolicyEnum.CPU_LIMIT) == 0;
        boolean isMemoryLimit = limitPolicy.compareTo(LimitPolicyEnum.MEMORY_LIMIT) == 0;
        boolean isTimeoutLimit = limitPolicy.compareTo(LimitPolicyEnum.TIMEOUT_LIMIT) == 0;
        boolean isExceptionLimit = limitPolicy.compareTo(LimitPolicyEnum.EXCEPTION_LIMIT) == 0;

        if (isAllPolicy) {
            putKeyToLimitMethodListByLimitPolocy(limitPolicyMethodsMap, CpuLoadLimitPolicy.class, key);
            putKeyToLimitMethodListByLimitPolocy(limitPolicyMethodsMap, MemoryUsageRateLimitPolicy.class, key);
            putKeyToLimitMethodListByLimitPolocy(limitPolicyMethodsMap, TimeoutRateLimitPolicy.class, key);
            putKeyToLimitMethodListByLimitPolocy(limitPolicyMethodsMap, ExceptionRateLimitPolicy.class, key);
            return;
        }
        if (isUsageRateLimit) {
            putKeyToLimitMethodListByLimitPolocy(limitPolicyMethodsMap, CpuLoadLimitPolicy.class, key);
            putKeyToLimitMethodListByLimitPolocy(limitPolicyMethodsMap, MemoryUsageRateLimitPolicy.class, key);
            return;
        }
        if (isExecuteResultLimit) {
            putKeyToLimitMethodListByLimitPolocy(limitPolicyMethodsMap, TimeoutRateLimitPolicy.class, key);
            putKeyToLimitMethodListByLimitPolocy(limitPolicyMethodsMap, ExceptionRateLimitPolicy.class, key);
        }
        if (isCpuLimit) {
            putKeyToLimitMethodListByLimitPolocy(limitPolicyMethodsMap, CpuLoadLimitPolicy.class, key);
            return;
        }
        if (isMemoryLimit) {
            putKeyToLimitMethodListByLimitPolocy(limitPolicyMethodsMap, MemoryUsageRateLimitPolicy.class, key);
            return;
        }
        if (isTimeoutLimit) {
            putKeyToLimitMethodListByLimitPolocy(limitPolicyMethodsMap, TimeoutRateLimitPolicy.class, key);
            return;
        }
        if (isExceptionLimit) {
            putKeyToLimitMethodListByLimitPolocy(limitPolicyMethodsMap, ExceptionRateLimitPolicy.class, key);
            return;
        }

    }

    /**
     * 检查是否需要去注册度量器
     * 
     * @param classDynamicLimit
     * @param methodDynamicLimit
     */
    private static boolean checkNeedRegistryMetric(DynamicLimit classDynamicLimit, DynamicLimit methodDynamicLimit) {
        // 不含有限流注解,无需注册度量工具
        if (methodDynamicLimit == null && classDynamicLimit == null) {
            return false;
        }
        // 方法上含有限流注解但关闭了限流开关,无需注册度量工具
        if (methodDynamicLimit != null && methodDynamicLimit.value() == false) {
            return false;
        }
        // 方法上没有注解注解,类上限流注解但关闭了限流开关,无需注册度量工具
        if (methodDynamicLimit == null && classDynamicLimit != null && classDynamicLimit.value() == false) {
            return false;
        }
        return true;
    }

    /**
     * @param limitPolicyMethodsMap
     * @param key
     * @return
     */
    private static List<String> putKeyToLimitMethodListByLimitPolocy(Map<String, List<String>> limitPolicyMethodsMap,
        Class<? extends AbstractLimitPolicy> policyclass, String key) {
        List<String> limitMethodlist = limitPolicyMethodsMap.get(policyclass.getName());
        if (limitMethodlist == null) {
            limitPolicyMethodsMap.put(policyclass.getName(), new ArrayList<String>());
        }
        List<String> list = limitPolicyMethodsMap.get(policyclass.getName());
        list.add(key);
        return list;

    }

    /**
     * 根据http请求信息注册相关的度量工具(比如IP)
     * 
     * @param limitData
     * @param method
     * @param request
     */
    public static void registryMetric(DynamicLimitData dynamicLimitData, HandlerMethod handlerMethod,
        HttpServletRequest request) {
        toRegistryMetric(dynamicLimitData, handlerMethod,
            CommonLimitUtils.getKeyByIpQuarantine(handlerMethod, request));
    }

    /**
     * 注册指定的度量器
     * 
     * @param dynamicLimitData
     * @param name
     * @param metric
     */
    public static void registryGlobalMetric(DynamicLimitData dynamicLimitData) {
        MetricRegistry metricRegistry = dynamicLimitData.getMetricRegistry();
        // 度量内存使用率
        metricRegistry.register(CommonLimitConstants.METRIC_AVERAGE_MEMORY, new SlidingHistory());
    }

}